# 指定文件编码为UTF-8
# coding: utf-8

"""
simple_convnet.py 功能说明：
1. 实现一个简单的卷积神经网络(CNN)结构
2. 网络结构：Conv -> ReLU -> Pool -> Affine -> ReLU -> Affine -> Softmax
3. 支持卷积层、池化层、全连接层等基本操作
4. 提供前向传播、反向传播、参数保存/加载等功能
5. 包含数值梯度和误差反向传播两种梯度计算方法

关键点：
- 使用OrderedDict有序管理网络层
- 支持卷积参数自定义(滤波器数量、大小、步长、填充)
- 实现SoftmaxWithLoss作为最后一层
- 提供数值梯度验证反向传播实现
- 参数可保存为pkl文件
"""

# 导入系统模块
import sys, os
# 添加父目录到系统路径
sys.path.append(os.pardir)
# 导入pickle模块用于参数序列化
import pickle
# 导入NumPy数值计算库
import numpy as np
# 导入有序字典类
from collections import OrderedDict
# 从common.layers导入所有网络层
from common.layers import *
# 从common.gradient导入数值梯度计算
from common.gradient import numerical_gradient


class SimpleConvNet:
    """简单的卷积神经网络类

    网络结构：
    conv - relu - pool - affine - relu - affine - softmax

    参数说明
    ----------
    input_dim : 输入维度 (通道, 高, 宽)
    conv_param : 卷积参数字典
        filter_num: 滤波器数量
        filter_size: 滤波器大小
        pad: 填充大小
        stride: 步长
    hidden_size : 隐藏层神经元数量
    output_size : 输出层神经元数量
    weight_init_std : 权重初始化标准差
    """
    def __init__(self, input_dim=(1, 28, 28),
                 conv_param={'filter_num':30, 'filter_size':5, 'pad':0, 'stride':1},
                 hidden_size=100, output_size=10, weight_init_std=0.01):
        # 获取卷积参数
        filter_num = conv_param['filter_num']
        filter_size = conv_param['filter_size']
        filter_pad = conv_param['pad']
        filter_stride = conv_param['stride']
        input_size = input_dim[1]
        # 计算卷积和池化后的输出尺寸
        conv_output_size = (input_size - filter_size + 2*filter_pad) / filter_stride + 1
        pool_output_size = int(filter_num * (conv_output_size/2) * (conv_output_size/2))

        # 初始化权重参数
        self.params = {}
        # 第1层卷积权重 (滤波器数量, 输入通道, 滤波器高, 滤波器宽)
        self.params['W1'] = weight_init_std * \
                            np.random.randn(filter_num, input_dim[0], filter_size, filter_size)
        self.params['b1'] = np.zeros(filter_num)  # 卷积偏置
        # 第2层全连接权重
        self.params['W2'] = weight_init_std * \
                            np.random.randn(pool_output_size, hidden_size)
        self.params['b2'] = np.zeros(hidden_size)  # 全连接偏置
        # 第3层全连接权重
        self.params['W3'] = weight_init_std * \
                            np.random.randn(hidden_size, output_size)
        self.params['b3'] = np.zeros(output_size)  # 输出层偏置

        # 按顺序构建网络层
        self.layers = OrderedDict()
        # 第1层: 卷积层
        self.layers['Conv1'] = Convolution(self.params['W1'], self.params['b1'],
                                           conv_param['stride'], conv_param['pad'])
        # 第2层: ReLU激活
        self.layers['Relu1'] = Relu()
        # 第3层: 池化层(2x2, stride=2)
        self.layers['Pool1'] = Pooling(pool_h=2, pool_w=2, stride=2)
        # 第4层: 全连接层
        self.layers['Affine1'] = Affine(self.params['W2'], self.params['b2'])
        # 第5层: ReLU激活
        self.layers['Relu2'] = Relu()
        # 第6层: 全连接输出层
        self.layers['Affine2'] = Affine(self.params['W3'], self.params['b3'])

        # 最后一层: SoftmaxWithLoss
        self.last_layer = SoftmaxWithLoss()

    def predict(self, x):
        """前向传播计算输出"""
        for layer in self.layers.values():
            x = layer.forward(x)
        return x

    def loss(self, x, t):
        """计算损失函数值"""
        y = self.predict(x)  # 前向传播
        return self.last_layer.forward(y, t)  # 计算损失

    def accuracy(self, x, t, batch_size=100):
        """计算分类准确率"""
        if t.ndim != 1 : t = np.argmax(t, axis=1)  # 将one-hot转为标签

        acc = 0.0  # 初始化准确率

        # 分批计算避免内存不足
        for i in range(int(x.shape[0] / batch_size)):
            tx = x[i*batch_size:(i+1)*batch_size]  # 当前批输入
            tt = t[i*batch_size:(i+1)*batch_size]  # 当前批标签
            y = self.predict(tx)  # 前向传播
            y = np.argmax(y, axis=1)  # 获取预测类别
            acc += np.sum(y == tt)  # 累计正确数

        return acc / x.shape[0]  # 计算总体准确率

    def numerical_gradient(self, x, t):
        """数值梯度计算(用于验证反向传播)"""
        loss_w = lambda w: self.loss(x, t)  # 定义损失函数

        grads = {}
        # 计算各层参数的数值梯度
        for idx in (1, 2, 3):
            grads['W' + str(idx)] = numerical_gradient(loss_w, self.params['W' + str(idx)])
            grads['b' + str(idx)] = numerical_gradient(loss_w, self.params['b' + str(idx)])

        return grads

    def gradient(self, x, t):
        """误差反向传播法计算梯度"""
        # 前向传播
        self.loss(x, t)

        # 反向传播
        dout = 1  # 初始化梯度
        dout = self.last_layer.backward(dout)  # 最后一层反向传播

        # 各层反向传播
        layers = list(self.layers.values())
        layers.reverse()  # 反转层顺序
        for layer in layers:
            dout = layer.backward(dout)

        # 收集各层梯度
        grads = {}
        grads['W1'], grads['b1'] = self.layers['Conv1'].dW, self.layers['Conv1'].db
        grads['W2'], grads['b2'] = self.layers['Affine1'].dW, self.layers['Affine1'].db
        grads['W3'], grads['b3'] = self.layers['Affine2'].dW, self.layers['Affine2'].db

        return grads

    def save_params(self, file_name="params.pkl"):
        """保存网络参数到文件"""
        params = {}
        for key, val in self.params.items():
            params[key] = val
        with open(file_name, 'wb') as f:
            pickle.dump(params, f)  # 序列化参数

    def load_params(self, file_name="params.pkl"):
        """从文件加载网络参数"""
        with open(file_name, 'rb') as f:
            params = pickle.load(f)  # 反序列化参数
        # 更新网络参数
        for key, val in params.items():
            self.params[key] = val

        # 更新各层参数
        for i, key in enumerate(['Conv1', 'Affine1', 'Affine2']):
            self.layers[key].W = self.params['W' + str(i+1)]
            self.layers[key].b = self.params['b' + str(i+1)]

"""
使用说明：
1. 实例化网络：
   net = SimpleConvNet(input_dim=(1,28,28), conv_param={'filter_num':30, ...}, ...)
2. 训练网络：
   - 前向传播：y = net.predict(x)
   - 计算损失：loss = net.loss(x, t)
   - 计算梯度：grads = net.gradient(x, t)
3. 保存/加载参数：
   - net.save_params()
   - net.load_params()
4. 验证梯度：
   - 比较numerical_gradient和gradient的结果
"""
